package server;

import interfacePaxos.Constant;
import interfacePaxos.PaxosRemote;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.RemoteServer;
import java.rmi.server.ServerNotActiveException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

public class LockServer extends UnicastRemoteObject implements PaxosRemote {

  private Acceptor my_acceptor;
  private Thread my_acceptor_thread;
  private Thread my_acceptor_monitor;
  private Learner my_learner;
  private Proposer my_proposer;
  private List<PaxosRemote> my_servers;
  private int my_id;
  private boolean client_ok; // a client has OKed server to load config

  private static final long serialVersionUID = 1L;
  private static final String CONFIGFILE = "config.txt";

  public LockServer() throws RemoteException {
    super();
    client_ok = false;
  }

  /**
   * @param args
   *          the command line arguments
   * @throws NotBoundException
   * @throws IOException
   */
  public static void main(String[] args) throws AlreadyBoundException,
      NotBoundException, IOException {

    try {
      Registry registry = LocateRegistry.createRegistry(Constant.port);
      PaxosRemote lm = new LockServer();
      registry.bind(Constant.RMI_ID, lm);
      System.out.println("Server is started: "
          + Arrays.toString(registry.list()));
    } catch (Exception e) {
      System.err.println("PaxosRemote exception:");
      e.printStackTrace();
    }
  }

  /**
   * 
   * @throws IOException
   * @throws RemoteException
   * @throws NotBoundException
   */
  public void setServer() throws IOException, RemoteException,
      NotBoundException {
    if (!client_ok) {
      System.out.println("Loading config...");
      my_servers = new ArrayList<PaxosRemote>();

      BufferedReader br = new BufferedReader(new FileReader(CONFIGFILE));

      List<PaxosRemote> nodes = new ArrayList<PaxosRemote>();

      String line = br.readLine();
      String[] hosts = line.split(",");
      System.out.println(Arrays.toString(hosts));

      for (String h : hosts) {
        Registry registry = LocateRegistry.getRegistry(h, Constant.port);
        PaxosRemote pr = (PaxosRemote) registry.lookup(Constant.RMI_ID);
        nodes.add(pr);
      }
      br.close();
      my_servers = nodes;
      my_id = findServerId();
      System.out.println("Server ID: " + my_id);
      my_acceptor = new Acceptor(my_id, my_servers);
      my_acceptor_thread = new Thread(my_acceptor);
      my_acceptor_thread.start();
      my_acceptor_monitor = new Thread(new Runnable() {
        @Override
        public void run() {
          while (true) {
            if (my_acceptor_thread.isAlive()) {
              Random r = new Random();
              int temp = r.nextInt(5);
              try {
                Thread.sleep(10000 + (temp * 1000)); // acceptor lives for 10
                                                     // seconds
                my_acceptor.fail(); // simulate failure manually
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
            } else {
              try {
                Thread.sleep(5000);
                System.out
                    .println("Acceptor failure detected, spawning new Acceptor thread...");
                my_acceptor = new Acceptor(my_id, my_servers);
              } catch (IOException e) {
                e.printStackTrace();
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
              my_acceptor_thread = new Thread(my_acceptor);
              my_acceptor_thread.start();
            }
          }
        }
      });
      my_acceptor_monitor.start();
      System.out.println("Acceptor started");
      my_learner = new Learner(my_id, my_servers);
      System.out.println("Learner started");
      my_proposer = new Proposer(my_id, my_servers);
      System.out.println("Proposer started");
      System.out.println("Config loaded");
      client_ok = true;
    }
  }

  // precondition: server list has been set
  private int findServerId() throws RemoteException, NotBoundException {
    Registry registry = LocateRegistry.getRegistry("127.0.0.1", Constant.port);
    PaxosRemote pr = (PaxosRemote) registry.lookup(Constant.RMI_ID);
    return my_servers.indexOf(pr);
  }

  @Override
  public boolean request(boolean lock, int number) throws IOException {
    boolean result;
    System.out.println("====================================================");
    String lease_client = "";
    try {
      lease_client = RemoteServer.getClientHost();
      System.out.println("Request from client: " + lease_client);
    } catch (ServerNotActiveException e1) {
      e1.printStackTrace();
    }
    if (!lock && !my_learner.checkLock(lock, number)) {
      System.out.println("Lock is already open.");
      result = false;
    } else if (lock && my_learner.checkLock(lock, number)) {
      if (lease_client.equals(my_learner.getLockHolder(number))) {
        System.out
            .println(lease_client + " already holds lock " + number + ".");
      } else {
        System.out.println("Lock held by someone else.");
      }
      result = false;
    } else if (!lock && !lease_client.equals(my_learner.getLockHolder(number))) {
      System.out.println("Client does not own lock.");
      result = false;
    } else { // if request is valid
      my_proposer.request(lease_client, lock, number);
      System.out.println("Request sent.");
      my_proposer.sendPrepareProposal();
      result = my_learner.checkLock(lock, number);
      System.out.println("Proposal result: " + (result == lock));
    }
    System.out.println("====================================================");
    return result;
  }

  /**
   * 
   * @param p
   */
  @Override
  public void notifyLearner(Proposal p) {
    my_learner.receiveAcceptedProposal(my_id, p);
  }

  /**
   * 
   * @param my_id
   * @param b
   * @param my_last_accepted_proposal
   * @return
   */
  @Override
  public boolean promise(int my_id, boolean b,
      Proposal my_last_accepted_proposal) {
    if (b == true) {
      try {
        my_proposer.promise(my_id, b, my_last_accepted_proposal);
      } catch (IOException ex) {
        Logger.getLogger(LockServer.class.getName())
            .log(Level.SEVERE, null, ex);
        return false;
      }
    } else {
      my_proposer.handleNack(my_last_accepted_proposal);
    }
    return b;
  }

  @Override
  public Proposal getProposalNumber() throws RemoteException {
    System.out.println("Learner not null : " + (my_learner != null));
    return my_learner.getProposalNumber();
  }

  public void prepare(final Proposal the_proposal) throws IOException {
    my_acceptor.prepare(the_proposal);
  }

  public boolean accept(final Proposal the_proposal) throws IOException {
    return my_acceptor.accept(the_proposal);
  }
}
